import {
  Component,
  OnInit,
  ViewEncapsulation,
  ChangeDetectionStrategy,
  Input,
  EventEmitter,
  Output,
  TemplateRef,
  ChangeDetectorRef,
  OnDestroy,
  forwardRef,
  TrackByFunction,
  OnChanges,
  SimpleChanges,
} from '@angular/core';
import { animate, group, query, stagger, style, transition, trigger, AnimationEvent } from '@angular/animations';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { Subject, of, type Observable } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { remove as _remove, filter as _filter } from 'lodash';
import { MotionDuration, MotionFunc, noAnimationToVoid } from '../core/animation/api';
import { isEmpty, isFirefoxBelowRecentVersions } from '../core/utils/common';
import { InputBoolean } from '../core/utils/convert';
import { LvTagAlarmStatus } from '../core/types';
import { TypeUtils } from '../core/utils/type';
import { TagItem, TagEventOutput, getBeforeStream } from './interface';

@Component({
  selector: 'lv-tag',
  templateUrl: './tag.component.html',
  animations: [
    trigger('tagMotion', [
      noAnimationToVoid,
      transition('* => *', [
        query(
          '.lv-tag-item:leave',
          [
            style({ transform: 'scale(1)', opacity: 1, overflow: 'hidden' }),
            stagger(0, [
              group([
                animate(`${MotionDuration.XS} ${MotionFunc.EASE_IN_OUT}`, style({ opacity: 0 })),
                animate(
                  `${MotionDuration.XS} ${MotionFunc.EASE}`,
                  style({
                    transform: 'scale(0.8)',
                    width: 0,
                    padding: 0,
                    margin: 0,
                  }),
                ),
              ]),
            ]),
          ],
          { optional: true },
        ),
        query(
          '.lv-tag-item:enter',
          [
            style({ transform: 'scale(0.8)', opacity: 0, width: 0, padding: 0, margin: 0, overflow: 'hidden' }),
            stagger(0, [
              group([
                animate(`${MotionDuration.XS} ${MotionFunc.EASE_IN_OUT}`, style({ opacity: 1 })),
                animate(`${MotionDuration.XS} ${MotionFunc.EASE}`, style({ transform: 'scale(1)' })),
              ]),
            ]),
          ],
          { optional: true },
        ),
      ]),
    ]),
  ],
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef((): typeof TagComponent => TagComponent),
      multi: true,
    },
  ],
  encapsulation: ViewEncapsulation.None,
  changeDetection: ChangeDetectionStrategy.OnPush,
  host: {
    class: 'lv-tag',
    '[class.lv-tag-alarm]': 'lvAlarmStatus',
    '[class.lv-tag-alarm-critical]': 'lvAlarmStatus === "critical"',
    '[class.lv-tag-alarm-major]': 'lvAlarmStatus === "major"',
    '[class.lv-tag-alarm-minor]': 'lvAlarmStatus === "minor"',
    '[class.lv-tag-alarm-warning]': 'lvAlarmStatus === "warning"',
  },
})
export class TagComponent implements ControlValueAccessor, OnInit, OnChanges, OnDestroy {
  @Input() lvChecked: TagItem[] = []; // 选中项
  @Input() lvMaxWidth: string | number;
  @Input() lvAlarmStatus: LvTagAlarmStatus;
  @Input() @InputBoolean() lvCheckable = false; // 是否支持选中
  // 输入输出属性
  @Input() @InputBoolean() lvShowClearAll = false; // 是否支持清除所有
  @Output() lvClearAll = new EventEmitter<void>(); // 清除所有回调
  @Output() lvRemove = new EventEmitter<TagItem>(); // 清除单个回调
  @Output() lvItemClick = new EventEmitter<TagItem>(); // 内部综合筛选器使用，对外API不体现
  @Input() lvTagTemplate: TemplateRef<{ $implicit: any }>; // 自定义tag template
  @Output() lvCheckedChange = new EventEmitter<TagItem[]>(); // 选中回调
  @Input() lvBeforeRemove: (value: TagItem) => TagEventOutput; // 删除单个tag前回调，返回false时阻止删除
  @Output() lvMotionDone = new EventEmitter<AnimationEvent>(); // 内部综合筛选器使用，对外API不体现
  @Input() lvExtraContent: TemplateRef<{ $implicit: TagItem[] }>; // 扩展内容 template
  @Input() lvBeforeClearAll: (value: TagItem[]) => TagEventOutput; // 删除所有前回调，返回false时阻止删除
  // 外部可访问变量
  tagList: TagItem[];

  isFirefoxBelowRecentVersions: boolean;
  tagMaxWidth: string | number = '200px';
  // 内部变量
  private _refresh$ = new Subject<void>();
  private _destroy$ = new Subject<void>();
  constructor(private _cdr: ChangeDetectorRef) {}

  get hasRemoveItem(): boolean {
    /** @deprecated: 平滑过渡，使用removable替代，后续删除 */
    return !!_filter(this.tagList, (item): any => !item.disabled && (item.removeable || item.removable)).length;
  }

  @Input() lvTrackBy: TrackByFunction<any> = (_, item: TagItem): string => item.label; // trackBy

  setDisabledState?(): void {}

  setRefresh(): void {
    this._refresh$.next();
  }

  _updateData(): void {
    this._onChange(this.tagList);
  }

  registerOnChange(fn: any): void {
    this._onChange = fn;
  }

  registerOnTouched(fn: any): void {
    this._onTouch = fn;
  }

  ngOnDestroy(): void {
    this._destroy$.next();
    this._destroy$.complete();
  }

  clearable(data: TagItem): boolean {
    return (data.removeable || data.removable) && !(data.disabled || data.disabeld);
  }

  ngOnChanges(changes: SimpleChanges): void {
    if (changes.lvMaxWidth) {
      this.getTagItemMaxWidth(changes.lvMaxWidth.currentValue);
    }
  }

  judgeIsChecked(source: TagItem): TagItem {
    return this.lvCheckable && this.lvChecked?.find((item): boolean => item?.label === source?.label);
  }

  _getBeforeRemoveStream(source: TagItem): Observable<boolean> {
    return getBeforeStream(this.lvBeforeRemove ? this.lvBeforeRemove(source) : of(true));
  }

  _getBeforeClearStream(source: TagItem[]): Observable<boolean> {
    return getBeforeStream(this.lvBeforeClearAll ? this.lvBeforeClearAll(source) : of(true));
  }

  writeValue(value: any): void {
    this.tagList = value;
    if (this.isFirefoxBelowRecentVersions) {
      this.lvMotionDone.emit();
    }
    this.setRefresh();
  }

  ngOnInit(): void {
    this.isFirefoxBelowRecentVersions = isFirefoxBelowRecentVersions();
    this._refresh$.pipe().subscribe((): void => this._cdr.markForCheck());
  }

  getTagItemMaxWidth(val: string | number): void {
    if (Number.isNaN(val) || isEmpty(val)) {
      return;
    }
    this.tagMaxWidth = TypeUtils.isValidNumber(val) || Number(val) ? `${val}px` : val;
  }

  clickItem(e: MouseEvent, source: TagItem): void {
    e.stopPropagation();
    if (source.disabled) {
      return;
    }
    this.lvItemClick.emit(source);
    if (this.lvCheckable) {
      this.handleChecked(source);
    }
  }

  removeItem(e: MouseEvent, source: TagItem): void {
    e.stopPropagation();
    if (source.disabeld || source.disabled) {
      return;
    }

    this._getBeforeRemoveStream(source)
      .pipe(takeUntil(this._destroy$))
      .subscribe((res): void => {
        if (!res) {
          return;
        }
        _remove(this.tagList, (item): boolean => item === source);
        if (this.isFirefoxBelowRecentVersions) {
          this.lvMotionDone.emit();
        }
        this._updateData();
        this.lvRemove.emit(source);
        this.setRefresh();
      });
  }

  handleChecked(source: TagItem): void {
    let checkedData = this.lvChecked || [];
    const checkedIndex = checkedData.findIndex((item): boolean => item.label === source.label);
    if (isEmpty(checkedIndex) || checkedIndex === -1) {
      checkedData = [...(checkedData || []), source];
    } else {
      checkedData = checkedData.filter((item): boolean => item.label !== source.label);
    }
    const checkedDataValue = checkedData.map((item): any => item.label) || [];
    this.lvChecked = this.tagList?.filter((item): boolean => checkedDataValue.includes(item.label)) || [];
    this.lvCheckedChange.emit(checkedData);
  }

  clearAll(e: MouseEvent): void {
    e.stopPropagation();

    if (!this.hasRemoveItem) {
      return;
    }

    this._getBeforeClearStream(this.tagList)
      .pipe(takeUntil(this._destroy$))
      .subscribe((res): void => {
        if (!res) {
          return;
        }
        /** @deprecated: 平滑过渡，使用removable替代，后续删除 */
        this.tagList = _filter(this.tagList, (item): boolean => item.disabled || !(item.removeable || item.removable));
        if (this.isFirefoxBelowRecentVersions) {
          this.lvMotionDone.emit();
        }
        this._updateData();
        this.lvClearAll.emit();
        this.setRefresh();
      });
  }

  private _onTouch: any = (): void => {};

  private _onChange: any = (): void => {};
}
